//==================================================================
//  Copyright (C) 2006  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
///
///
///
///
///
//==================================================================

#include "stdafx.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "psys.h"
#include "rendm5d_mate.h"
#include "rendm5d_mesh.h"
#include "rendm5d_displist.h"

//==================================================================
using namespace PMath;

//==================================================================
RendM5D::Mesh::Mesh( M5D::Mesh *mmeshp, RendDevice *rdevicep ) :
	_mmeshp(mmeshp),
	_rdevicep(rdevicep)
{
	_mmeshp->_userdatap = (void *)this;
	
	UpdateGeometry();
}

//==================================================================
void RendM5D::Mesh::UpdateGeometry()
{
	// for the time being, just erase any of these three lists if it's changed
	// ..they will be rebuilt if an when needed again
	if ( _tri_id_dlistp && _tri_id_dlistp->_revcnt > 0 )
		SAFE_DELETE( _tri_id_dlistp );

	if ( _obj_id_dlistp && _obj_id_dlistp->_revcnt > 0 )
		SAFE_DELETE( _obj_id_dlistp );

	if ( _onoff_dlistp && _onoff_dlistp->_revcnt > 0 )
		SAFE_DELETE( _onoff_dlistp );

	_mmeshp->UpdateIdxDispList();
	_mmeshp->UpdateVtxDispList();
}
/*
//==================================================================
void RendM5D::Mesh::RenderBBox()
{
	float	*bbox[2];
	bbox[0] = (float *)_mmeshp->GetBBoxMin().v;
	bbox[1] = (float *)_mmeshp->GetBBoxMax().v;
#ifdef TARGET_D3D
	// xxxx
#else
	glBegin( GL_LINES );
		for (int i=0; i < 2; ++i)
		{
			glVertex3f( bbox[0][0], bbox[0][1], bbox[i][2] );
			glVertex3f( bbox[1][0], bbox[0][1], bbox[i][2] );	glVertex3f( bbox[1][0], bbox[0][1], bbox[i][2] );
			glVertex3f( bbox[1][0], bbox[1][1], bbox[i][2] );	glVertex3f( bbox[1][0], bbox[1][1], bbox[i][2] );
			glVertex3f( bbox[0][0], bbox[1][1], bbox[i][2] );	glVertex3f( bbox[0][0], bbox[1][1], bbox[i][2] );
			glVertex3f( bbox[0][0], bbox[0][1], bbox[i][2] );

			for (int j=0; j < 2; ++j)
			{
				glVertex3f( bbox[i][0], bbox[j][1], bbox[0][2] );
				glVertex3f( bbox[i][0], bbox[j][1], bbox[1][2] );
			}
		}
	glEnd();
#endif
}
*/

//==================================================================
//------------------------------------------------------------------
bool RendM5D::Mesh::drawGeometry( const M5D::DispList *dlistp, bool render_opaque )
{
	PASSERT( dlistp->_vert_def.IsActiveUsage( M5D::VType::POS ) );

	bool	skipped_anything = false;

	bool	dlist_has_tex = dlistp->HasTex();
	bool	dlist_has_col = dlistp->HasCol();
	bool	dlist_has_nor = dlistp->HasNor();

	_rdevicep->_ShaderMng._Std.SelectStdVDef( dlist_has_tex, dlist_has_col, dlist_has_nor );

#if defined(TARGET_D3D)
	RendM5D::DispList	*rdlistp = (RendM5D::DispList *)dlistp->_userdatap;

	rdlistp->SelectBuffers();

	static D3DPRIMITIVETYPE native_type_table[ M5D::Primitive::TYPE_N ] =
	{
		D3DPT_LINELIST,		// TYPE_LINELIST
		D3DPT_TRIANGLELIST, // TYPE_TRILIST
		D3DPT_QUADLIST,		// TYPE_QUADLIST
	};

	D3DDevice	*d3ddevp = _rdevicep->GetD3DDevice();

	DispList::Pool	*p		= dlistp->GetPrimPool(0);
	DispList::Pool	*pend	= p + dlistp->GetPrimPoolsN();

	if ( dlistp->IsIndexType() )
	{
		for (; p != pend; ++p)
		{
			Material		*mmatep = p->GetMaterial();
			if ( mmatep->IsOpaque() != render_opaque )
			{
				skipped_anything = true;
				continue;
			}

			int	n_prims = p->GetPrimsN();
			if NOT( n_prims )
				continue;
 
			RendM5D::Material	*rmatep = (RendM5D::Material *)mmatep->_userdatap;
			rmatep->SelectMaterial();
			_rdevicep->_ShaderMng.SelectShader(	dlist_has_tex && (mmatep->GetTexture() != NULL),
												dlist_has_col,
												dlist_has_nor );

			d3ddevp->DrawIndexedPrimitive( native_type_table[ p->GetPrimType() ],
											0,
											0,
											0,
											p->GetOffset(),
											n_prims );
/*
			d3ddevp->DrawIndexedPrimitiveUP(	native_type_table[ p->GetPrimType() ],
												0,
												dlistp->_verts_n,
												p->GetPrimsN(),
												dlistp->GetFirstIndexPtr() + p->GetOffset(),
												D3DFMT_INDEX16,
												(u_char *)dlistp->_vert_datap[0],
												dlistp->_vert_stride );
*/
		}
	}
	else
	{
		for (; p != pend; ++p)
		{
			Material		*mmatep = p->GetMaterial();
			if ( mmatep->IsOpaque() != render_opaque )
			{
				skipped_anything = true;
				continue;
			}

			int	n_prims = p->GetPrimsN();
			if NOT( n_prims )
				continue;

			RendM5D::Material	*rmatep = (RendM5D::Material *)mmatep->_userdatap;
			rmatep->SelectMaterial();
			_rdevicep->_ShaderMng.SelectShader(	dlist_has_tex && (mmatep->GetTexture() != NULL),
												dlist_has_col,
												dlist_has_nor );

			d3ddevp->DrawPrimitive( native_type_table[ p->GetPrimType() ],
									p->GetOffset(),
									n_prims );
/*
			d3ddevp->DrawPrimitiveUP(	native_type_table[ p->GetPrimType() ],
										p->GetPrimsN(),
										(u_char *)dlistp->_vert_datap[0],
										dlistp->_vert_stride );
*/

		}
	}

#elif defined(TARGET_OPENGL)
	_rdevicep->_ShaderMng.SelectVertexStream(
				dlistp->_vert_def.GetVertPtr( 0, M5D::VType::POS ),
				dlistp->_vert_def.GetVertPtr( 0, M5D::VType::TEX ),
				dlistp->_vert_def.GetVertPtr( 0, M5D::VType::COL ),
				dlistp->_vert_def.GetVertPtr( 0, M5D::VType::NOR ),
				dlistp->_vert_def._stride );

	static u_int native_type_table[ M5D::Primitive::TYPE_N ] =
	{
		GL_LINES,		// TYPE_LINELIST
		GL_TRIANGLES,	// TYPE_TRILIST
		GL_QUADS,		// TYPE_QUADLIST
	};

	glLockArraysEXT( 0, dlistp->_vert_def._n_verts );	// lock arrays for speed

		const M5D::DispList::Pool	*p		= dlistp->GetPrimPool(0);
		const M5D::DispList::Pool	*pend	= p + dlistp->GetPrimPoolsN();
		if ( dlistp->IsIndexType() )
		{
			for (; p != pend; ++p)
			{
				const M5D::Material		*mmatep = p->GetMaterial();
				if ( mmatep->IsOpaque() != render_opaque )
				{
					skipped_anything = true;
					continue;
				}

				RendM5D::Material	*rmatep = (RendM5D::Material *)mmatep->_userdatap;
				_rdevicep->_ShaderMng._Std.SelectShader(	dlist_has_tex && (mmatep->GetTexture() != NULL),
															dlist_has_col,
															dlist_has_nor );
				rmatep->SelectMaterial();

				int		nverts = p->GetVertsN();
				const u_short	*ptr = dlistp->GetFirstIndexPtr() + p->GetOffset();

				glDrawElements( native_type_table[ p->GetPrimType() ],
								nverts,
								GL_UNSIGNED_SHORT,
								ptr );
			}
		}
		else
		{
			for (; p != pend; ++p)
			{
				const M5D::Material		*mmatep = p->GetMaterial();
				if ( mmatep->IsOpaque() != render_opaque )
				{
					skipped_anything = true;
					continue;
				}

				RendM5D::Material	*rmatep = (RendM5D::Material *)mmatep->_userdatap;
				_rdevicep->_ShaderMng._Std.SelectShader(	dlist_has_tex && (mmatep->GetTexture() != NULL),
															dlist_has_col,
															dlist_has_nor );
				rmatep->SelectMaterial();

				glDrawArrays( native_type_table[ p->GetPrimType() ], p->GetOffset(), p->GetVertsN() );
			}
		}

	glUnlockArraysEXT();	// unlock the arrays
#endif

	return skipped_anything;
}

//==================================================================
//------------------------------------------------------------------
bool RendM5D::Mesh::drawGeometryNoMaterial( const M5D::DispList *dlistp, bool render_opaque )
{
	PASSERT( dlistp->_vert_def.IsActiveUsage( M5D::VType::POS ) );

	bool	skipped_anything = false;

#ifdef TARGET_D3D
	RendM5D::DispList	*rdlistp = (RendM5D::DispList *)dlistp->_userdatap;

	rdlistp->SelectBuffers();

	static D3DPRIMITIVETYPE native_type_table[ M5D::Primitive::TYPE_N ] =
	{
		D3DPT_LINELIST,		// TYPE_LINELIST
		D3DPT_TRIANGLELIST, // TYPE_TRILIST
		D3DPT_QUADLIST,		// TYPE_QUADLIST
	};

	D3DDevice	*d3ddevp = _rdevicep->GetD3DDevice();

	DispList::Pool	*p		= dlistp->GetPrimPool(0);
	DispList::Pool	*pend	= p + dlistp->GetPrimPoolsN();

	if ( dlistp->IsIndexType() )
	{
		for (; p != pend; ++p)
		{
			d3ddevp->DrawIndexedPrimitive( native_type_table[ p->GetPrimType() ],
											0,
											0,
											0,
											p->GetOffset(),
											p->GetPrimsN() );
		}
	}
	else
	{
		for (; p != pend; ++p)
		{
			d3ddevp->DrawPrimitive( native_type_table[ p->GetPrimType() ],
									p->GetOffset(),
									p->GetPrimsN() );
		}
	}

#elif defined(TARGET_OPENGL)
	_rdevicep->_ShaderMng.SelectVertexStream(	dlistp->_vert_def.GetVertPtr( 0, M5D::VType::POS ),
											dlistp->_vert_def.GetVertPtr( 0, M5D::VType::TEX ),
											dlistp->_vert_def.GetVertPtr( 0, M5D::VType::COL ),
											dlistp->_vert_def.GetVertPtr( 0, M5D::VType::NOR ),
											dlistp->_vert_def._stride );

	static u_int native_type_table[ M5D::Primitive::TYPE_N ] =
	{
		GL_LINES,		// TYPE_LINELIST
		GL_TRIANGLES,	// TYPE_TRILIST
		GL_QUADS,		// TYPE_QUADLIST
	};

	glLockArraysEXT( 0, dlistp->_vert_def._n_verts );	// lock arrays for speed

		const M5D::DispList::Pool	*p		= dlistp->GetPrimPool(0);
		const M5D::DispList::Pool	*pend	= p + dlistp->GetPrimPoolsN();
		if ( dlistp->IsIndexType() )
		{
			for (; p != pend; ++p)
			{
				glDrawElements( native_type_table[ p->GetPrimType() ],
								p->GetVertsN(),
								GL_UNSIGNED_SHORT,
								dlistp->GetFirstIndexPtr() + p->GetOffset() );
			}
		}
		else
		{
			for (; p != pend; ++p)
			{
				glDrawArrays( native_type_table[ p->GetPrimType() ], p->GetOffset(), p->GetVertsN() );
			}
		}

	glUnlockArraysEXT();	// unlock the arrays
#endif

	return skipped_anything;
}

//------------------------------------------------------------------
bool RendM5D::Mesh::drawVertNormals( const M5D::DispList *dlistp, bool render_opaque )
{
	bool	skipped_anything = false;

	if ( !dlistp->_vert_def.IsActiveUsage( M5D::VType::POS ) ||
		 !dlistp->_vert_def.IsActiveUsage( M5D::VType::NOR ) )
	{
		return skipped_anything;
	}

	_rdevicep->_ShaderMng._Std.SelectShader( false, false, false );
	_rdevicep->_ShaderMng._Std.SetBaseColor( Vector4(0,1,0,1), Vector3(0,0,0), 0, Vector4(0,1,0,1) );

#ifdef TARGET_D3D

	// ...

#elif defined(TARGET_OPENGL)

	int	n_dest_verts = dlistp->_vert_def._n_verts * 2;

	Vector3	*vertsp = new Vector3[ n_dest_verts ];

	const Vector3	*src_posp = (const Vector3 *)dlistp->_vert_def.GetVertPtr( 0, M5D::VType::POS );
	const Vector3	*src_norp = (const Vector3 *)dlistp->_vert_def.GetVertPtr( 0, M5D::VType::NOR );
	int		src_stride = dlistp->_vert_def._stride;

	for (int i=0; i < dlistp->_vert_def._n_verts; ++i)
	{
		vertsp[ i*2 + 0 ] = *src_posp;
		vertsp[ i*2 + 1 ] = *src_posp + *src_norp * 0.4f;

		src_posp = (const Vector3 *)((const u_char *)src_posp + src_stride);
		src_norp = (const Vector3 *)((const u_char *)src_norp + src_stride);
	}

	_rdevicep->_ShaderMng.SelectVertexStream(	vertsp,
											NULL,
											NULL,
											NULL,
											sizeof(Vector3) );

	//glColor3f( 0, 1, 0 );

	//glLockArraysEXT( 0, n_dest_verts );	// lock arrays for speed

		glDrawArrays( GL_LINES, 0, n_dest_verts );

	//glUnlockArraysEXT();	// unlock the arrays

	delete [] vertsp;

#endif

	return skipped_anything;
}

//==================================================================
void RendM5D::Mesh::BeginRenderState_s( RendDevice *rdevicep, RendM5D::Params &rparams, bool render_opaque )
{
	rdevicep->_DrawState.SetZWrite( render_opaque ? true : false );
	//rdevicep->_DrawState.SetZTest( true );
	rdevicep->_DrawState.SetCulling( PSYSGFX::CULL_OFF );

	switch ( rparams.render_type )
	{
	case RENDTYPE_STANDARD:
		break;
	case RENDTYPE_VERT_NORMALS:
	case RENDTYPE_TRI_ID:
	case RENDTYPE_TRI_ONOFFCOL:
	case RENDTYPE_MODEL_ID:
		rdevicep->_DrawState.SetBlending( false );
		rdevicep->_DrawState.SetTexture( NULL );
		break;
	case RENDTYPE_BBOX:
		break;
	}
}

//==================================================================
void RendM5D::Mesh::EndRenderState_s( RendDevice *rdevicep )
{
}

//==================================================================
void RendM5D::Mesh::prepareTriOnOff( const u_char *tri_onoffmap, int idx_base, const float on_col[4], const float off_col[4] ) throw(...)
{
	int r1 = (int)(on_col[0] * 255.0f);
	int g1 = (int)(on_col[1] * 255.0f);
	int b1 = (int)(on_col[2] * 255.0f);
	int a1 = (int)(on_col[3] * 255.0f);

	int r2 = (int)(off_col[0] * 255.0f);
	int g2 = (int)(off_col[1] * 255.0f);
	int b2 = (int)(off_col[2] * 255.0f);
	int a2 = (int)(off_col[3] * 255.0f);

	u_int	on_col_uint		= (r1 << 0) | (g1 << 8) | (b1 << 16) | (a1 << 24);
	u_int	off_col_uint	= (r2 << 0) | (g2 << 8) | (b2 << 16) | (a2 << 24);

	if ( !_onoff_dlistp )
	{
		SAFE_DELETE( _onoff_dlistp );
		_onoff_dlistp = _mmeshp->_mod_scenep->NewDispList();

		M5D::MultiVerts		&verts = _mmeshp->GetVerts();
		M5D::Triangles		&trigs = _mmeshp->GetTrigs();
		verts.LockR( __FILE__, __LINE__ );
		trigs.LockR( __FILE__, __LINE__ );
		_onoff_dlistp->RebuildOnOffDList(	trigs,
											tri_onoffmap,
											idx_base,
											on_col_uint,
											off_col_uint,
											verts.GetPosR(),
											verts.GetNorR() );
		verts.Unlock();
		trigs.Unlock();
	}
	else
	{
		_onoff_dlistp->UpdateOnOffDList( tri_onoffmap, idx_base, on_col_uint, off_col_uint );
	}
}

//==================================================================
void RendM5D::Mesh::PrepareStandard( RendM5D::Params &rparams )
{
	PASSERT( rparams.render_type == RENDTYPE_STANDARD );
	UpdateGeometry();
/*
	M5D::DispList	*dlistp;

	if ( dlistp = _mmeshp->GetDispListVertex() )
	{
		RendM5D::DispList	*rdlistp = (RendM5D::DispList *)dlistp->_userdatap;
		rdlistp->UpdateBuffers();
	}

	if ( dlistp = _mmeshp->GetDispListIndex() )
	{
		RendM5D::DispList	*rdlistp = (RendM5D::DispList *)dlistp->_userdatap;
		rdlistp->UpdateBuffers();
	}
*/
}

//==================================================================
bool RendM5D::Mesh::RenderStandard( RendM5D::Params &rparams, bool render_opaque )
{
	PASSERT( rparams.render_type == RENDTYPE_STANDARD );

	bool	skipped_anything = false;

	if ( _mmeshp->GetDispListVertex() )
		skipped_anything |= drawGeometry( _mmeshp->GetDispListVertex(), render_opaque );

	if ( _mmeshp->GetDispListIndex() )
		skipped_anything |= drawGeometry( _mmeshp->GetDispListIndex(), render_opaque );

	return skipped_anything;
}

//==================================================================
void RendM5D::Mesh::PrepareSpecial( RendM5D::Params &rparams )
{
	PASSERT( rparams.render_type != RENDTYPE_STANDARD );
	UpdateGeometry();

	RendM5D::DispList	*rdlistp;

	switch ( rparams.render_type )
	{
	case RENDTYPE_VERT_NORMALS:
		break;

	case RENDTYPE_TRI_ID:
		if NOT( _tri_id_dlistp )
		{
			_tri_id_dlistp = _mmeshp->_mod_scenep->NewDispList();

			M5D::MultiVerts		&verts = _mmeshp->GetVerts();
			M5D::Triangles		&trigs = _mmeshp->GetTrigs();
			verts.LockR( __FILE__, __LINE__ );
			trigs.LockR( __FILE__, __LINE__ );

			_tri_id_dlistp->RebuildTriIDDList( trigs, rparams.cur_rend_id, verts.GetPosR() );

			verts.Unlock();
			trigs.Unlock();
		}
		
		rdlistp = (RendM5D::DispList *)_tri_id_dlistp->_userdatap;
		rdlistp->UpdateBuffers();
		break;

	case RENDTYPE_TRI_ONOFFCOL:
		prepareTriOnOff( rparams.tri_onoffmap, rparams.tri_onoff_idxbase, rparams.tri_onoff_oncol, rparams.tri_onoff_offcol );
		break;

	case RENDTYPE_MODEL_ID:
		if NOT( _obj_id_dlistp )
		{
			_obj_id_dlistp = _mmeshp->_mod_scenep->NewDispList();

			M5D::MultiVerts		&verts = _mmeshp->GetVerts();
			M5D::Triangles		&trigs = _mmeshp->GetTrigs();
			verts.LockR( __FILE__, __LINE__ );
			trigs.LockR( __FILE__, __LINE__ );

			_obj_id_dlistp->RebuildPosOnlyDList( trigs, verts.GetPosR(), verts.GetPosN() );

			verts.Unlock();
			trigs.Unlock();
		}
		break;

	case RENDTYPE_PLAIN:
		UpdateGeometry();
		break;

	case RENDTYPE_BBOX:
		break;
	}
}

//==================================================================
void RendM5D::Mesh::RenderSpecial( RendM5D::Params &rparams )
{
	PASSERT( rparams.render_type != RENDTYPE_STANDARD );

	switch ( rparams.render_type )
	{
	case RENDTYPE_VERT_NORMALS:
		if ( _mmeshp->GetDispListVertex() )
			drawVertNormals( _mmeshp->GetDispListVertex(), true );

		if ( _mmeshp->GetDispListIndex() )
			drawVertNormals( _mmeshp->GetDispListIndex(), true );
		break;

	case RENDTYPE_TRI_ID:
		if NOT( _mmeshp->GetTrigs()._list.size() )
			return;

		if NOT( _tri_id_dlistp )
		{
			_tri_id_dlistp = _mmeshp->_mod_scenep->NewDispList();

			M5D::MultiVerts		&verts = _mmeshp->GetVerts();
			M5D::Triangles		&trigs = _mmeshp->GetTrigs();
			verts.LockR( __FILE__, __LINE__ );
			trigs.LockR( __FILE__, __LINE__ );

			_tri_id_dlistp->RebuildTriIDDList( trigs, rparams.cur_rend_id, verts.GetPosR() );

			verts.Unlock();
			trigs.Unlock();
		}
		_rdevicep->_ShaderMng._Std.SelectShader( 
			_tri_id_dlistp->HasTex(),
			_tri_id_dlistp->HasCol(),
			_tri_id_dlistp->HasNor() );
		drawGeometryNoMaterial( _tri_id_dlistp, true );
		break;

	case RENDTYPE_TRI_ONOFFCOL:
		_rdevicep->_ShaderMng._Std.SelectShader( 
			_onoff_dlistp->HasTex(),
			_onoff_dlistp->HasCol(),
			_onoff_dlistp->HasNor() );
		drawGeometryNoMaterial( _onoff_dlistp, true );
		break;

	case RENDTYPE_MODEL_ID:
		{
			u_int	id = rparams.cur_rend_id;
			Vector4	emiss(	((id >>  0) & 255) / 255.0f,
							((id >>  8) & 255) / 255.0f,
							((id >> 16) & 255) / 255.0f,
							((id >> 24) & 255) / 255.0f );

			_rdevicep->_ShaderMng._Std.SetBaseColor( Vector4(0,0,0,0), Vector3(0,0,0), 0, emiss );
			_rdevicep->_ShaderMng._Std.SelectShader( 
				_obj_id_dlistp->HasTex(),
				_obj_id_dlistp->HasCol(),
				_obj_id_dlistp->HasNor() );
			drawGeometryNoMaterial( _obj_id_dlistp, true );
		}
		break;

	case RENDTYPE_PLAIN:
		if ( _mmeshp->GetDispListVertex() )
			drawGeometryNoMaterial( _mmeshp->GetDispListVertex(), true );

		if ( _mmeshp->GetDispListIndex() )
			drawGeometryNoMaterial( _mmeshp->GetDispListIndex(), true );
		break;

	case RENDTYPE_BBOX:
		break;
	}
}
/*
//==================================================================
void RendM5D::Mesh::RendPlainOneFace( int tri_idx )
{
	Vector3		*posp = _mmeshp->GetVerts().GetPos();
	triangle_t	*trip = &_mmeshp->GetTrigs()._list[ tri_idx ];

#ifdef TARGET_D3D
	// xxxx
#else
	glBegin( GL_TRIANGLES );
		glVertex3fv( posp[ trip->pos[ 0 ] ].v );
		glVertex3fv( posp[ trip->pos[ 1 ] ].v );
		glVertex3fv( posp[ trip->pos[ 2 ] ].v );
	glEnd();
#endif
}
*/
